#!/usr/bin/php
<?php
/* Copyright 2020-2022, Dan Landon
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 */

/* Set the level for debugging. */
$DEBUG_LEVEL	= (! $argv[4]) ? 0 : (int) $argv[4];

/* Get the comand to execute. */
$COMMAND	= $argv[1];

$plugin			= "unassigned.devices";
$docroot		= $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
$config_file	= "/tmp/{$plugin}/config/samba_mount.cfg";
$tc 			= "/var/state/{$plugin}/ping_status.json";
$var			= @parse_ini_file("$docroot/state/var.ini");

/* Misc functions. */
class MiscUD
{
	/* Get content from a json file. */
	public function get_json($file) {
		return file_exists($file) ? @json_decode(file_get_contents($file), true) : array();
	}

	/* Save content to a json file. */
	public function save_json($file, $content) {
		@file_put_contents($file."-", json_encode($content, JSON_PRETTY_PRINT));
		@rename($file."-", $file);
	}

	/* Confirm we have a good ip address. */
	public function is_ip($str) {
		return filter_var($str, FILTER_VALIDATE_IP);
	}

	/* Check for text in a file. */
	public function exist_in_file($file, $text) {
		return (preg_grep("%{$text}%", @file($file))) ? true : false;
	}

}

/* Unassigned Devices logging. */
function unassigned_log($m, $debug_level = 0) {
	global $plugin;

	if (($debug_level == 0) || ($debug_level == $GLOBALS["DEBUG_LEVEL"])) {
		$m		= print_r($m,true);
		$m		= str_replace("\n", " ", $m);
		$m		= str_replace('"', "'", $m);
		exec("/usr/bin/logger"." ".escapeshellarg($m)." -t ".escapeshellarg($plugin));
	}
}

/* Run a command and time out if it doesn't complete in the $timeout number of seconds. */
function timed_exec($timeout = 10, $cmd) {
	$time		= -microtime(true); 
	$out		= shell_exec("/usr/bin/timeout ".escapeshellarg($timeout)." ".$cmd);
	$time		+= microtime(true);
	if ($time >= $timeout) {
		unassigned_log("Warning: shell_exec(".$cmd.") took longer than ".sprintf('%d', $timeout)."s!");
		$out	= "command timed out";
	} else {
		unassigned_log("Timed Exec: shell_exec(".$cmd.") took ".sprintf('%f', $time)."s!", 3);
	}

	return $out;
}

/* Is the server currently on line. */
function is_server_online($server, $log = false) {
	global $tc, $var;

	/* See if we need to add this server to the hosts file. This seems to be an issue with Windows computers. */
	if (! MiscUD::is_ip($server)) {
		$ip_arp		= timed_exec(10, "/sbin/arp -a ".escapeshellarg($server)." 2>&1");
		$ip_array	= explode(" ", $ip_arp);
		$ip			= str_replace(array("(", ")"), "", $ip_array[1]);
		$ip			= (($ip_array[0] == $server) && (MiscUD::is_ip($ip))) ? $ip : "";

		/* If arp did not return a valid IP address, look up the IP address using Local TLD. */
		if (strpos($ip_arp, "no match found") === false) {
			$local_tld	= $var['LOCAL_TLD'];
			$srvr		= $server.".".($local_tld ? $local_tld : "local");
			$ip_local	= timed_exec(10, "/sbin/arp -a ".escapeshellarg($srvr)." 2>&1");
			$ip_array	= explode(" ", $ip_local);
			$ip_local	= str_replace(array("(", ")"), "", $ip_array[1]);
			$ip_local	= MiscUD::is_ip($ip_local) ? $ip_local : "";
		} else {
			$ip_local	= $ip;
		}

		/* If the ip address does not match the .local ip address, add/update entry in hosts file. */
		if ($ip_local) {
			if (($ip != $ip_local) && (MiscUD::is_ip($ip_local))) {
				shell_exec("/bin/sed -i '/".escapeshellarg($server)."/d' /etc/hosts");
				shell_exec("/bin/echo -e '".escapeshellarg($ip_local)."''\t''".escapeshellarg($server)."' >> /etc/hosts" );
			}
		} else if ((strpos($ip_arp, "no match found") === false) && ($log)) {
			unassigned_log("Remote server '".$server."' cannot be found on the LAN.");
		}
	}

	/* Check the last ping status. */
	$ping_status	= MiscUD::get_json($tc);
	$was_alive		= ($ping_status[$server]['online'] == 'yes') ? true : false;
	$is_alive		= (trim(exec("/bin/ping -c 1 -W 1 ".escapeshellarg($server)." >/dev/null 2>&1; echo $?")) == 0 ) ? true : false;
	$no_pings		= isset($ping_status[$server]['no_pings']) ? $ping_status[$server]['no_pings'] : 0;
	if (! $is_alive && ! MiscUD::is_ip($server))
	{
		$ip			= trim(timed_exec(5, "/usr/bin/nmblookup ".escapeshellarg($server)." | /bin/head -n1 | /bin/awk '{print $1}' 2>/dev/null"));
		if (MiscUD::is_ip($ip))
		{
			$is_alive = (trim(exec("/bin/ping -c 1 -W 1 ".escapeshellarg($ip)." >/dev/null 2>&1; echo $?")) == 0 ) ? true : false;
		}
	}

	/* If it is not online then start counts for being offline. */
	if (! $is_alive) {
		/* Check for three consecutive negative pings before declaring it is off-line. */
		$no_pings++;
		if (($no_pings <= 3) && ($ping_status[$server]['online'] == 'yes')) {
			$is_alive = true;
		} else if ($no_pings > 3){
			$no_pings = 0;
		}
	} else {
		$no_pings = 0;
	}

	/* When the server first goes offline, log a message. */
	if ($was_alive != $is_alive) {
		if (! $is_alive) {
			unassigned_log("Remote server '".$server."' is not responding to a ping and appears to be offline.");
		}
		$changed = true;
	} else {
		$changed = false;
	}

	/* Update the server status file. */
	$ping_status[$server] = array('no_pings' => $no_pings, 'online' => $is_alive ? 'yes' : 'no', 'changed' => $changed ? 'yes' : 'no');
	MiscUD::save_json($tc, $ping_status);

	return $changed;
}

/* Ping all remote servers to check for being on-line. */
function ping_servers() {
	global $config_file, $tc;

	/* Refresh the ping status. */
	$samba_mounts	= @parse_ini_file($config_file, true);
	if (is_array($samba_mounts)) {
		foreach ($samba_mounts as $device => $mount) {
			/* This updates the ping_status file with the current state of the remote server. */
			$server			= $mount['ip'];
			$changed		= is_server_online($server);

			/* Update server ping status if it has changed. */
			if ($changed) {
				$ping_status	= MiscUD::get_json($tc);
				$no_pings		= $ping_status[$server]['no_pings'];
				$online			= $ping_status[$server]['online'];
				$ping_status[$server] = array('no_pings' => $no_pings, 'online' => $online, 'changed' => 'no');
				MiscUD::save_json($tc, $ping_status);
			}
		}
	}
}

/* Get the size, used, and free space on device. */
function df_status($tc, $mountpoint) {

	$df_status	= MiscUD::get_json($tc);
	$rc			= trim(timed_exec(5, "/bin/df ".escapeshellarg($mountpoint)." --output=size,used,avail | /bin/grep -v '1K-blocks' 2>/dev/null"));
	$df_status[$mountpoint] = array('timestamp' => time(), 'stats' => $rc);
	MiscUD::save_json($tc, $df_status);
}

switch ($COMMAND) {
	case 'ping':
		ping_servers();
		break;

	case 'df_status':
		df_status($argv[2], $argv[3]);
		break;

	case 'is_online':
		is_server_online($argv[2], true);
		break;

	default:
		exit(0);
		break;
}
?>
